package main

import (
	"encoding/json"
	"errors"
	"log"
	"net/http"
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

const (
	// 允许等待的写入时间
	writeWait = 10 * time.Second
	// Time allowed to read the next pong message from the peer.
	pongWait = 0
	// Send pings to peer with this period. Must be less than pongWait.
	pingPeriod = (pongWait * 9) / 10
	// Maximum message size allowed from peer.
	maxMessageSize = 40960
)

var upgrader = websocket.Upgrader{
	ReadBufferSize:  1024,
	WriteBufferSize: 1024,
	// 允许所有的CORS 跨域请求，正式环境可以关闭
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}

// 客户端读写消息
type wsMessage struct {
	// websocket.TextMessage 消息类型
	messageType int
	data        []byte
}

// 客户端连接
type WsConnection struct {
	wsSocket *websocket.Conn // 底层websocket
	inChan   chan *wsMessage // 读队列
	outChan  chan *wsMessage // 写队列

	mutex             sync.Mutex // 避免重复关闭管道,加锁处理
	isClosed          bool
	closeChan         chan byte // 关闭通知
	id                int64
	lastHeartBeatTime time.Time // 上次心跳包时间
	needHeartBeat     bool      // 上次心跳
	server            *WsServer // 归属的服务端
}

// 读取消息队列中的消息
func (wsConn *WsConnection) wsRead() (*wsMessage, error) {
	tick := time.Tick(time.Second * 1)
	select {
	case msg := <-wsConn.inChan:
		// 获取到消息队列中的消息
		return msg, nil
	case <-wsConn.closeChan:
	case <-tick:
		return nil, errors.New("超时")
	}
	return nil, errors.New("连接已经关闭")
}

// 处理队列中的消息
func (this *WsConnection) PayloadParseAndCallback(dat []byte) error {
	var payload Request
	e := json.Unmarshal(dat, &payload)
	if nil != e {
		log.Print(e.Error())
		return e
	}
	log.Print(payload)
	return nil
}

// 处理队列中的消息
func (wsConn *WsConnection) processLoop(this *WsServer) {
	// 处理消息队列中的消息
	// 获取到消息队列中的消息，处理完成后，发送消息给客户端
	for {
		msg, err := wsConn.wsRead()
		if err != nil {
			if err.Error() == "超时" {
				// log.Print(wsConn.lastHeartBeatTime.String())
				if wsConn.needHeartBeat {
					if time.Now().Sub(wsConn.lastHeartBeatTime) > time.Second*15 {
						log.Print("心跳超时")
						wsConn.Close()
					}
				}
				continue
			}
			break
		}
		ProtoCallBack(wsConn, msg.data)
	}
}

type Response struct {
	Type    int         `json:"type"`
	Payload interface{} `json:"data"`
}

// 发送信息
func (wsConn *WsConnection) SendPayload(ptype int64, v interface{}) error {
	var resp Response
	resp.Type = int(ptype)
	resp.Payload = v
	bytes, e := json.Marshal(resp)
	if nil != e {
		log.Print(e.Error())
	}
	wsConn.wsWrite(1, bytes)
	return nil
}

// 写入消息到队列中
func (wsConn *WsConnection) wsWrite(messageType int, data []byte) error {
	select {
	case wsConn.outChan <- &wsMessage{messageType, data}:
	case <-wsConn.closeChan:
		return errors.New("连接已经关闭")
	}
	return nil
}

// 处理消息队列中的消息
func (wsConn *WsConnection) wsReadLoop(server *WsServer) {
	// 设置消息的最大长度
	wsConn.wsSocket.SetReadLimit(maxMessageSize)
	for {
		// 读一个message
		msgType, data, err := wsConn.wsSocket.ReadMessage()
		req := &wsMessage{
			msgType,
			data,
		}
		if server.callback != nil {
			server.callback.OnDataRecv(wsConn, data)
		}
		if err != nil {
			log.Print(err.Error())
			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
				log.Println("消息读取出现错误", err.Error())
			}
			wsConn.Close()
			if server.callback != nil {
				server.callback.OnDisConected(wsConn)
			}
			return
		}
		log.Print(string(data))
		// 放入请求队列,消息入栈
		select {
		case wsConn.inChan <- req:
		case <-wsConn.closeChan:
			return
		}
	}
}

// 发送消息给客户端
func (wsConn *WsConnection) wsWriteLoop(server *WsServer) {
	for {
		select {
		// 取一个应答
		case msg := <-wsConn.outChan:
			// 写给websocket
			if err := wsConn.wsSocket.WriteMessage(msg.messageType, msg.data); err != nil {
				log.Println("发送消息给客户端发生错误", err.Error())
				// 切断服务
				wsConn.Close()
				if server.callback != nil {
					server.callback.OnDisConected(wsConn)
				}
				return
			}
		case <-wsConn.closeChan:
			// 获取到关闭通知
			return
		}
	}
}

// 关闭连接
func (wsConn *WsConnection) Close() {
	wsConn.wsSocket.Close()
	wsConn.mutex.Lock()

	defer wsConn.mutex.Unlock()
	if !wsConn.isClosed {
		wsConn.isClosed = true
		close(wsConn.closeChan)
	}
	ProtoUnconnect(wsConn)
	wsConn.server.DelConection(wsConn)
}
